李智慧 高并发架构实战课(一)

01 | 软件建模与文档:架构师怎样绘制系统架构蓝图?

我们主要的手段就是软件建模,以及将这些软件模型组织成一篇有 价值的软件设计文档。

软件建模

不同的开发工程师会清晰自己开发的模块和其他同事工作内容的关系与依赖,并按照这些模型开发代码。
如何建模?——>两个客观存在

  • 一个是我们要解决的领域问题
  • 另一个客观存在就是最终开发出来的软件系统(组成、依赖、调用、部署、通信等)

对领域问题和软件系统进行分析、设计和抽象的这个过程,就是软件建模设计。

软件设计方法

UML 包含的软件 模型有 10 种,其中常用的有 7 种:类图、序列图、组件图、部署图、用例图、状态图和 活动图。
(推荐你阅读马丁富勒的《UML 精粹》一书)

类图

用来描述类的特性和类之间的静态关系。
一个类包含三个部分:类的名字、类的属性列表和类的方法列表。类之间有 6 种静态关 系:关联、依赖、组合、聚合、继承、泛化。

时序图

用来描述参与者之间的动态调用关系。
每个参与者有一条垂直向下的生命线,这条线用虚线表示。而参与者之 间的消息从上到下表示其调用的前后顺序关系
只要是描述不同参与者之间交互的,都可以使用时序图。

组件图

组件图描述组件之间的静态关系,主要是依赖关系,如果你想要描述组件之间的动态调用关系,可以使用组件时序图,以组件作为参与者,描述组件之间的消息调用关系。

部署图

是在设计早期就需要画的一种模型图。根据部署图,各方可以讨论对这个方案是否认可

用例图

通过反映用户和软件系统的交互,描述系统的功能需求。

状态图

状态图用来展示单个对象生命周期的状态变迁。

活动图

主要用来描述过程逻辑和业务流程。UML 中没有流程图,很多时候,人们用活动图代替流程图。
实心圆代表流程开始,空心圆代表流程结束,圆角矩形表示活动,菱形表示分支判断。
泳道:活动图可以根据活动的范围,将活动根据 领域、系统和角色等划分到不同的泳道中,使流程边界更加清晰

软件设计文档

软件设计过程可以拆分成需求分析、概要设计和详细设计三个阶段。

需求分析阶段

用例图:描述系统的功能与使用场景;
活动图:对于关键的业务流程
时序图:描述新系统和原来的子系统的调用关系
状态图:某些对象内部会有复杂的状态变化

概要设计阶段

部署图:描述系统最终的物理蓝图
组件图以及组件时序图:设计软件主要模块及其关系
组件活动图:描述组件间的流程逻辑。

详细设计阶段

类图和类的时序图:指导最终的代码开发,如果某个类方法内部有比较复杂的逻辑,那么可以将这个方法的逻辑用活动图进行描述。

架构师应该针对不同的相关方, 使用不同的模型图输出不同的架构文档。

02 | 高并发架构设计方法:面对高并发,怎么对症下药?

高并发系统架构的方法论

核心就是为了满足用户的高并发访问,系统需要提供更多的计算资源。
解决方案大致可以分成两类,一类是传统大型软件系统的技术方案,被称作垂直伸缩方案。所谓的垂直伸缩就是提升单台服务器的处理能力
另一类解决方案,水平伸缩,指的是使用更多的服务器,将这些服务器构成一个分布式集群,通过这个集群,对外统一提供服务,以此来提高系统整体的处理能力。
水平伸缩除了还有一个天然的好处,那就是具有更好的弹性。因此现在的大型互联网系统多采取水平伸缩方案,来应对用户的高并发访问。

高并发系统架构的方法

主要技术方法,其核心是各种分布式技术。

分布式应用

主要手段就是使用负载均衡服务器,将多台应用服务器构成一个分布式集 群,用户请求首先到达负载均衡服务器,然后由负载均衡服务器将请求分发到不同的应用 服务器上

分布式缓存

分布式消息队列

分布式消息队列是解决突发的高并发写操作问题和实现更简单的集群伸缩的一种常用技术方案。消息队列架构主要包含三个角色:消息生产者、消息队列、消息消费者

分布式关系数据库

为了解决关系数据库存储海量数据以及提供高并发读写的问题,人们提出了将数据进行分片,再将 不同分片写入到不同数据库服务器的方法。

分布式微服务

微服务的核心思想是将单体架构中庞大的业务逻辑拆分成一些更小、更低耦合的服务,然 后通过服务间的调用完成业务的处理。

微服务架构的实现需要依赖一个微服务框架,这个框架包括一个微服务注册中心和一个 RPC 远程调用框架。微服务客户端通过注册中心得到要调用的微服务具体的地址列表,然 后通过一个软负载均衡算法选择其中一个服务器地址,再通过 PRC 进行远程调用。
除了以上这些分布式技术,高并发系统中常用的还有大数据、分布式文件、区块 链、搜索引擎、NoSQL、CDN、反向代理等技术,也都是一些非常经典的分布式技术。

系统并发指标

  • 目标用户数
  • 系统用户数
  • 活跃用户数
  • 在线用户数
  • 并发用户数

有了上面这些用户数指标,我们就可以进一步估算架构设计需要考虑的其他一些技术指标,比如每天需要新增的文件存储空间,存储总系统用户需要的数据库规模,总网络带宽,每秒处理的请求数等等。

03 | 短 URL 生成器设计:百亿短 URL 怎样做到无冲突?

我们将设计开发一个短 URL 生成器,产品名称是“Fuxi(伏 羲)”。
我们预计 Fuxi 需要管理的短 URL 规模在百亿级别,并发吞吐量达到数万级别。这个量级 的数据对应的存储方案是什么样的?用传统的关系数据库存储,还是有其他更简单的办 法?此外,如何提升系统的并发处理能力呢?这些是我们今天要重点考虑的问题。

需求分析

短URL生成器的用例图

性能指标估算

  • 存储容量和并发量
  • 存储空间
  • 吞吐量
  • 网络带宽
    • 一般系统高峰期访问量是平均访问量的 2 倍
  • 网络带宽
  • 短URL长度估算
    • 646 ≈ 680亿,按我们前面评估,总 URL 数 120 亿,6 个字符的编码就可以满足需求。因此 Fuxi 的短 URL 编码长度 6 个字符

非功能需求

  1. 系统需要保持高可用,不因为服务器、数据库宕机而引起服务失效。
  2. 系统需要保持高性能,服务端 80% 请求响应时间应小于 5ms,99% 请求响应时间小于 20ms,平均响应时间小于 10ms。
  3. 短 URL 应该是不可猜测的,即不能猜测某个短 URL 是否存在,也不能猜测短 URL 可能 对应的长 URL 地址内容。

概要设计

设计核心就是短 URL 的生成。即长 URL 通过某种函数,计算得到一个6个字符的短URL。

单项散列函数生成短 URL

通常的设计方案是,将长 URL 利用 MD5 或者 SHA256 等单项散列算法,进行 Hash 计 算,得到 128bit 或者 256bit 的 Hash 值。然后对该 Hash 值进行 Base64 编码,得到 22 个或者 43 个 Base64 字符,再截取前面的 6 个字符,就得到短 URL 了,如图。

由于可能Hash冲突,所以在生成的时候,需要先校验该短 URL 是否已经映 射为其他的长 URL,如果是,那么需要重新计算
这样的冲突处理需要多次到存储中查找 URL,无法保证 Fuxi 的性能要求。

自增长短 URL

一种免冲突的算法是用自增长自然数来实现,即维持一个自增长的二进制自然数,然后将 该自然数进行 Base64 编码即可得到一系列的短 URL。这样生成的的短 URL 必然唯一
这种算法导致短URL可猜测,某时间段内的短链会集中在一个区间(可猜测)。Fuxi 的需求是不允许短URL可预测。

预生成短URL

先生成一批没有冲突的短 URL 字符串,当外 部请求输入长 URL 需要生成短 URL 的时候,直接从预先生成好的短 URL 字符串池中获取 一个即可。
预生成短 URL 的算法可以采用随机数来实现,6 个字符,每个字符都用随机数产生(用 0~63 的随机数产生一个 Base64 编码字符)。为了避免随机数产生的短 URL 冲突,需要 在预生成的时候检查该 URL 是否已经存在(用布隆过滤器检查)。因为预生成短 URL 是 离线的,所以这时不会有性能方面的问题。

Fuxi的整体部署模型

相对比较有挑战的就是高并发的读请求如何处理、预生成的短URL如何存储以及访问。高并发访问主要通过负载均衡与分布式缓存解决,而海量数据存储则通过HDFS以及HBase来完成。具体架构图如下。

系统调用可以分成两种情况,一种是用户请求生成短URL的过程;另一种是用户访问短URL,通过Fuxi跳转到长URL的过程。

  • 对于用户请求生成短 RUL 的过程,时序图如下
  • 对于用户通过客户端请求访问短 URL 的过程(即输入短 URL,请求返回长URL),时序图如下
  • 为了保证系统高可用,Fuxi 的应用服务器、文件服务器、数据库服务器都采用集群部署方 案,单个服务器故障不会影响 Fuxi 短 URL 的可用性。
  • 对于 Fuxi 的高性能要求,80% 以上的访问请求将被设计为通过缓存返回。Redis 的缓存响 应时间 1ms 左右,服务器端请求响应时间小于 3ms,满足 80% 请求小于 5ms 的性能目 标。对于缓存没有命中的数据,通过 HBase 获取,HBase 平均响应时间 10ms,也可以满 足设计目标中的性能指标。
  • 对于 Redis 缓存内存空间估算,业界一般认为,超过 80% 请求集中在最近 6 天生成的短 URL 上,Fuxi 主要缓存最近六天生成的短 URL 即可。根据需求容量估计,最近 6 天生成 的短 URL 数量约 1 亿条,因此需要 Redis 缓存服务器内存空间:1亿 × 1KB = 100GB。

详细设计

详细设计关注重定向响应码、短 URL 预生成文件及加载、用户自定义短URL等几个关键设计点。

重定向响应码

  • 其中 301 表示永久重 定向,即浏览器一旦访问过该短 URL,直接根据缓存在浏览器(HTTP 客户端)的长 URL 路径进行访问。
  • 302 表示临时重定向,每次访问短 URL 都需要访问短 URL 生成器。

Fuxi 的架构设计完全可以承受这些负载压力,因此 Fuxi 使用 302 状态码构造重 定向响应。

短URL预生成文件及预加载

Fuxi 的短 URL 是在系统上线前全部预生成的,并存储在 HDFS 文件中。共 144 亿个短URL,每个短 URL 6 个字符,文件大小 144亿 × 6B = 86.4GB。

由于预加载短 URL 服务器集群部署 多台服务器,应对同时加载相同短 URL 的情况,利用偏移量文件 对多个服务器进行互斥操作,即利用文件系统写操作锁的互斥性实现多服务器访问互斥。

应用程序的文件访问流程应该是:写打开偏移量文件 -> 读偏移量 -> 读打开短 URL 文件 - > 从偏移量开始读取 60K 数据 -> 关闭短 URL 文件 -> 修改偏移量文件 -> 关闭偏移量文件。

加载到预加载短 URL 服务器的 1 万个短 URL 会以链表的方式存储,每使用一个短 URL, 链表头指针就向后移动一位,并设置前一个链表元素的 next 对象为 null。这样用过的短 URL 对象可以被垃圾回收。

当剩余链表长度不足 2000 的时候,触发一个异步线程,从文件中加载 1 万个新的短 URL,并链接到链表的尾部。

用户自定义短 URL

Fuxi 限制用户自定义短 URL 的字符个 数,不允许用户使用 6 个字符的自定义短 URL,且 URL 长度不得超过 20 个字符。

生成自定义短 URL 的时候需要到数据库中检查冲突,是否指定的 URL 已经被使用,如果发生冲突,要求用户重新指定。

URL Base64 编码

其中“+”和“/”在 URL 中会被编码为“%2B”以及“%2F”,而“%”在写入数据库的 时候又和 SQL 编码规则冲突,需要进行再编码。

使用 URL 保留字符表以外的字 符对 Base64 编码表中的 62,63 进行编码:将“+”改为“-”,将“/”改为“_”
(这块有建议使用Base62编码,因为-和_字符在链接首尾显得有点突兀)

04 | 网页爬虫设计:如何下载千亿级网页?

需求分析

性能指标估算

  • 每月新增存储量
  • 总存储空间
  • TPS

非功能需求

  • 伸缩性:Bajie 可以灵活部署,扩大集群规模,增强其爬取网页的速度。也就是说,Bajie 必须是一个分布式爬虫。
  • 健壮性:能够面对各种异常,正常运行。
  • 去重:URL去重、内容去重
  • 扩展性:当前只需要爬取 HTML 页面即可,将来可能会扩展到图片、视频、文档等内容页面。

Bajie 必须是“礼貌的”。Bajie 要避免对同一个域名进行并发爬取,还要根据目标服务器的承载能力增加访问延迟,即在两次爬取访问之间,增加等待时间

Bajie 还需要遵循互联网爬虫协议,即目标网站的 robots.txt 协议,不爬取目标网站 禁止爬取的内容

概要设计

将遍历到的网页下载保存起来,就是爬虫的主要工作。
Bajie 不需要事先知道数千亿的 URL。Bajie 只需要知道一小部分 URL,也就是所谓的种子 URL。处理流程图如下

其中,URL 调度器是整个爬虫系统的中枢和核心,也是整个爬虫的驱动 器。爬虫就是靠着 URL 调度器源源不断地选择 URL,然后有节奏、可控地下载了整个互联 网,所以 URL 调度器也是爬虫的策略中心。
Bajie的部署图如下

分布式爬虫

详细设计

Bajie 详细设计关注 3 个技术关键点:URL 调度器算法、去重算法、高可用设计。

URL 调度器算法

URL 集合数量会随着页面不断下载而指数级增加。待下载 URL 数量将远远大于系统的下载能力,URL 调度器就需要决定当前先下载哪些 URL。

深度优先需要维护较为复杂的数据结构,而且太深的下载深度导致下载的页面非常分散,不利于我们构建搜索引擎和数据分析。所以我们没有使用深度优先算法。

使用队列的先进先出实现广度优先算法

爬虫应该优先爬取那些高质量 的网站。优先级和域名都可以使用不同队列来区分

去重算法

URL 去重可以使用布隆过滤器以提高效率。

Bajie 计算页面内容的 MD5 值,通过判断下载页面的内容 MD5 值是否已经存在,判断内 容是否重复。(比较内容重复的时候,需要将 HTML 里面的有效内容提取出来,也就是提取出去除 HTML 标签的文本信息,针对有效内容计算 MD5)。
查询MD5是否存在时,用布隆过滤器代替 Hash 表,以优化性能。

高可用设计

不需要像一般互联网系统那样进行高可用设计

URL 调度器和 URL 下载处理服务器都需要记录运行时状态,即存储本服务器已经加 载的 URL 和已经处理完成的 URL,这样宕机恢复的时候,就可以立刻读取到这些状态数据,进而使服务器恢复到宕机前的状态。对于 URL 下载处理服务器,Bajie 采用 Redis 记 录运行时状态数据。

对于 URL 下载处理服务器,Bajie 采用 Redis 记 录运行时状态数据。

URL 下载处理服务器会采用多线程(池)设 计。每个线程独立完成一个 URL 的下载和处理,线程也需要捕获各种异常,不会使自己因为网络超时或者解析异常而退出。

对于一个千亿级网页的爬虫系统而言,最主要的技术挑战应该是海量文件的存储与计算

05 | 网盘系统设计:万亿 GB 网盘如何实现秒传与限速?

网盘的主要技术挑战是海量数据的高并发读写访问

需求分析

DBox 的核心功能是提供文件上传和下载服务。

DBox 需要对上传和下载进行流速控制,保证付费用户得到更多的网络资源

负载指标估算

DBox 的设计目标是支持 10 亿用户注册使用,免费用户最大可拥有 1TB 存储空间。预计 日活用户占总用户的 20%,即 2 亿用户。每个活跃用户平均每天上传、下载 4 个文件。

DBox 的存储量、吞吐量、带宽负载估算如下。

  • 总存储量
    10亿 × 1T B = 10亿T B,去重后真正需要的存储空间大约是这个估算值的 10%
  • QPS
    系统需要满足的平均 QPS 约为 10000。
    2亿×4÷(24×60×60)≈ 1万
    高峰期 QPS 约为平均 QPS 的两倍,即 2 万。
  • 带宽负载
    每次上传下载文件平均大小 1MB,所以需要网络带宽负载 10GB/s,即 80Gb/s。
    1万× 1MB = 10GB/s = 80Gb/s 同样,高峰期带宽负载为 160Gb/s。

非功能需求

  1. 大数据量存储:10 亿注册用户,1000 亿个文件,约 1 亿 TB 的存储空间。
  2. 高并发访问:平均 1 万 QPS,高峰期 2 万 QPS。
  3. 大流量负载:平均网络带宽负载 80Gb/S,高峰期带宽负载160Gb/s。
  4. 高可靠存储:文件不丢失,持久存储可靠性达到 99.9999% ,即 100 万个文件最多丢失 (或损坏)1 个文件。
  5. 高可用服务:用户正常上传、下载服务可用性在 99.99% 以上,即一年最多 53 分钟不可用。6. 数据安全性:文件需要加密存储,用户本人及共享文件外,其他人不能查看文件内容。
  6. 不重复上传:相同文件内容不重复上传,也就是说,如果用户上传的文件内容已经被其他用户上传过了,该用户不需要再上传一次文件内容,进而实现“秒传”功能。从用户视角来看,不到一秒就可以完成一个大文件的上传。

概要设计

网盘设计的关键是元数据与文件内容的分离存储与管理

DBox 采用对象存储作为最终的文件存储方案,而对象存储不适合 存储大文件,需要进行切分。而大文件进行切分还带来其他的好处:可以以 Block 为单位进行上传和下载,提高文件传输速度;客户端或者网络故障导致文件传输失败,也只需要 重新传输失败的 Block 就可以,进而实现断点续传功能。

用户上传文件的时序图如下。

类似的,用户下载文件的时序图如下。

详细设计

元数据库设计


其中,User 表和 File 表为一对多的关系,File 表和 Block 表也是一对多的关系。

这 3 种表的记录数都是百亿级以上,所以元数据表采用分片的关系数据库存储。

因为查询的主要场景是根据用户 ID 查找用户信息和文件信息,以及根据文件 ID 查询 block 信息,所以 User 和 File 表都采用 user_id 作为分片键,Block 表采用 file_id 作为 分片键。

限速

API 服务器可以 根据用户类型,决定分配的 Block 服务器数目和 Block 服务器内的服务线程数,以及每个线程的上传、下载速率。

Block 服务器会根据 API 服务器的返回值,来控制客户端能够同时上传、下载的 Block 数 量以及传输速率,以此对不同用户进行限速。

秒传

DBox 需要通过更多信息判断文件是否相同:只有文件长度、文件开头 256KB 的 MD5 值、文件的 MD5 值,三个值都相同,才会认为文件相同。当文件长度小于 256KB,则直接上传文件,不启用秒传功能。

将原来的 File 表拆分成物理文件 表 Physics_File 和逻辑文件表 Logic_File。其中,Logic_File 记录用户文件的元数据,并和物理文件表 Physics_File 建立多对 1 关联关系,而 Block 表关联的则是 Physics_File 表,如下。

Logic_File 中字段 double_md5 记录了文件头 256KB 的 MD5、文件 MD5 两个数据拼接 后的数据,而 size 记录了文件长度,只有这两个字段都相同才会启用秒传。

应用架构师需要掌握的技术栈更加广泛,要能够掌握各种基础设施技术的特性,并能根据 业务特点选择最合适的方案;而基础设施架构师需要的技术栈更加深入

06 | 短视频系统设计:如何支持三千万用户同时在线看视频?

我们准备开发一个面向全球用户的短视频应用,用户总量预计 20 亿,应用名称: QuickTok。

QuickTok 的主要技术挑战是:如何应对高并发用户访问时的网络带宽压力,以及如何存储海量的短视频文件。

需求分析

QuickTok 的核心功能需求非常简单:用户上传视频、搜索视频、观看视频。我们将主要分析非功能需求。

  • 日活用户->日播放量->平均播放 QPS ->同时在观看的视频数
  • 平均需要每秒上传视频数->每秒上传至服务器的文件大小->每年新增视频需要的存储空间->总带宽

为了保证视频数据的高可用,不会因为硬盘损坏导致数据丢失,视频文件需要备份存储,QuickTok 采用双副本的备份存储策略,也就是每个视频文件存储三份

我们需要设计的短视频应用是一个每秒上传 550 个视频文件、11 万次播放、新增 165GB 存储以及 88Tb 总带宽的高并发应用系统。这个系统呢需要是高性能的,能迅速响应用户的上传和播放操作,也需要是高可用的,能面向全球用户提供 7 * 24 小时稳定的服务。

概要设计

核心部署模型如下图。

视频内容处理器是一个由责任链模式构建起来的管道。

视频上传环节的具体时序图如下。

对视频搜索及播放部分的设计,即核心部署模型图中标红的部分,如下。

视频搜索引擎会根据用户提交的视频标题、上传用户等元数据,以及视频内容处理器生成 的内容标签构建倒排索引

当用户点击缩略图时,App 开始播放视频。App 并不需要下载完整个视频文件才开始播放,而是以流的方式一边下载视频数据,一边播放。QuickTok 使用 MPEG–DASH 流媒体传输协议进行视频流传输,因为这个协议具 有自适应能力,而且支持 HTTP,可以应对 QuickTok 的视频播放需求。

详细设计

详细设计将关注视频存储系统、性能优化与 CDN。

视频存储系统设计

可以尝试与网盘相同的存储技术 方案,将视频文件拆分成若干 block,使用对象存储服务进行存储。

但 QuickTok 最终采用了另一种存储方案,即使用 Hadoop 分布式文件系统 HDFS 进行 存储。HDFS 适合大文件存储的一次写入多次读取的场景,满足视频一次上传多次播放的 需求;同时,它还可以自动进行数据备份(缺省配置下,每个文件存储三份),也满足我 们关于数据存储高可用的需求。

HDFS 适合存储大文件,大文件减少磁盘碎片,更有利于存储空间的利用,同时 HDFS NameNode 的访问压力也更小,所以我们需要把若干个视频文件合并成一个 HDFS 文件 进行存储,并将存储相关的细节记录到 HBase 中。

性能优化与 CDN 设计

如果用户的大部分请求都可以通过 CDN 返回,那么一方面可以极大加快用户请求的响应速度,另一方面又可以较大缓解数据中心的网络和硬盘负载压力,进一步提升应用整体的性能。

通常的 CDN 设计,是在 CDN 中没有用户请求的数据时,进行回源,即由 CDN 请求数据 中心返回需要的数据,然后缓存在 CDN 本地。

但 QuickTok 考虑到了短视频的特点:大 V、网红们发布的短视频会被更快速、更广泛地 播放。因此针对粉丝量超过 10 万的用户,系统将采用主动推送 CDN 的方法,以提高 CDN 的命中率,优化用户体验,如图:

如果是 10 万粉丝以上的用户 发布了短视频,CDN 推送服务会根据其粉丝活跃的区域,将视频推送到对应区域的 CDN 服务器上。

短视频的完播率通常不足 30%,所以 QuickTok 也不需要将完整视频推送到 CDN,只需 要根据视频发布者的历史播放记录,计算其完播率和播放期望进度,然后将短视频切分成 若干 chunk,将部分 chunk 推送到 CDN 即可。

业界一般共识,视频应用 CDN 处理的带宽大约占总带宽的 95% 以上,也就是说,通过合 理使用 CDN,QuickTok 数据中心需要处理的带宽压力不到 4Tb。

缩略图生成与推荐设计

我们需要通过大数据平台的机器学习引擎来完成缩略图的生成和推荐,如下图

缩略图的生成和推荐可以分为两个具体过程:

  • 实时在线的缩略图推荐过程 a;
    • 推荐引擎可以获取当前用户的偏好特征标签以及视频对应的多个缩略图特征,使用 XGboost 算法训练好的模型,将用户特征标签和缩略图特征进行匹配,然后返回最有可能 被当前用户点击的缩略图 ID。
  • 利用离线机器学习生成优质缩略图的过程 b。
    • 机器学习系统获取到了海量用户的浏览和点击数据,同时获取每个缩略图的特征。一方面,机器可以学习到,哪些特征的缩略图更容易获得用户点击,从而生成优质缩略图特征标签库;另一方面,机器还可以学习到每个用户自身更偏好的图像特征标签,供前面提到的推荐引擎使用。
    • 视频内容处理器可以使用随机的办法,抽取一些帧作为缩略图,进行冷启动。

关于视频转码的两个场景:一种是用户上传的不同的视频编码格式需要统一转码成平台格式;一种是转码成不同清晰度的视频文件,根据用户带宽和会员等级进行传输。